package NEHE.JOGL.Port;

import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLException;
import javax.media.opengl.glu.GLU;
import javax.swing.*;
import com.sun.opengl.util.FPSAnimator;
import com.sun.opengl.util.texture.Texture;
import com.sun.opengl.util.texture.TextureCoords;
import com.sun.opengl.util.texture.TextureIO;
import static javax.media.opengl.GL.*;

/**
 * NeHe Lesson 11: Flag Effect (Waving Texture)
 */
public class NeheJOGL11Flag extends JPanel implements GLEventListener {
   private static final int REFRESH_FPS = 60;    // Display refresh frames per second
   private GLU glu;             // For the GL Utility
   final FPSAnimator animator;  // Used to drive display() 
   GLCanvas canvas;
   
   // Divide the texture into 44x44 quads, with 45x45 points grid
   private int numPoints = 45;
   private float[][][] points = new float[numPoints][numPoints][3];
   int wiggleCount = 0; // Counter used to control how fast flag waves

   private static float rotateAnleX = 0; // rotational angle for x-axis in degree
   private static float rotateAnleY = 0; // rotational angle for y-axis in degree
   private static float rotateAngleZ = 0; // rotational angle for z-axis in degree
   private static float roateSpeedX = 0.05f; // rotational speed for x-axis
   private static float rotateSpeedY = 0.4f; // rotational speed for y-axis
   private static float rotateSpeedZ = 0.03f; // rotational speed for z-axis

   private Texture texture; // texture over the shape
   private String textureFileName = "images/tim.png";
   private String textureFileType = ".png";

   // Texture image flips vertically. Shall use TextureCoords class to retrieve the
   // top, bottom, left and right coordinates.
   private float textureTop;
   private float textureBottom;
   private float textureLeft;
   private float textureRight;

   // Constructor
   public NeheJOGL11Flag() {
      canvas = new GLCanvas();
      this.setLayout(new BorderLayout());
      this.add(canvas, BorderLayout.CENTER);
      canvas.addGLEventListener(this);
      //canvas.addKeyListener(this);
      //canvas.setFocusable(true);  // To receive key event
      //canvas.requestFocus();
   
      // Run the animation loop using the fixed-rate Frame-per-second animator,
      // which calls back display() at this fixed-rate (FPS).
      animator = new FPSAnimator(canvas, REFRESH_FPS, true);
   }

   // Main program
   public static void main(String[] args) {
      final int WINDOW_WIDTH = 320;
      final int WINDOW_HEIGHT = 240;
      final String WINDOW_TITLE = "Nehe #11: Flag Effect (Waving Texture)";

      JFrame frame = new JFrame();
      final NeheJOGL11Flag joglMain = new NeheJOGL11Flag();
      frame.setContentPane(joglMain);
      frame.addWindowListener(new WindowAdapter() {
         @Override 
         public void windowClosing(WindowEvent e) {
            // Use a dedicate thread to run the stop() to ensure that the
            // animator stops before program exits.
            new Thread() {
               @Override 
               public void run() {
                  joglMain.animator.stop(); // stop the animator loop
                  System.exit(0);
               }
            }.start();
         }
      });
      frame.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
      frame.setTitle(WINDOW_TITLE);
      frame.setVisible(true);
      joglMain.animator.start(); // start the animation loop
   }

   // ------ Implement methods declared in GLEventListener ------

   /**
    * Called by the drawable immediately after the OpenGL context is initialized for
    * the first time. Can be used to perform one-time OpenGL initialization such as
    * setup of lights and display lists. Run only once.
    */
   @Override
   public void init(GLAutoDrawable drawable) {
      // Get the OpenGL graphics context
      GL gl = drawable.getGL();
      // GL Utilities
      glu = new GLU();
      // Enable smooth shading, which blends colors nicely across a polygon, and
      // smoothes out lighting.
      gl.glShadeModel(GL_SMOOTH);
      // Set background color (in RGBA). Alpha of 0 for total transparency
      gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
      // Setup the depth buffer & enable the depth testing
      gl.glClearDepth(1.0f);
      gl.glEnable(GL_DEPTH_TEST); // enables depth testing
      gl.glDepthFunc(GL_LEQUAL); // the type of depth test to do
      // We want the best perspective correction to be done
      gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

      // Load the texture image
      try {
         // Create a OpenGL Texture object from (URL, mipmap, file suffix)
         // Use URL so that can read from JAR and disk file.
         texture = TextureIO.newTexture(
               this.getClass().getResource(textureFileName), false, textureFileType);
      } catch (GLException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      }

      // Use linear filter for texture if image is larger than the original texture
      gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      // Use linear filter for texture if image is smaller than the original texture
      gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

      // Texture image flips vertically. Shall use TextureCoords class to retrieve
      // the top, bottom, left and right coordinates, instead of using 0.0f and 1.0f.
      TextureCoords textureCoords = texture.getImageTexCoords();
      textureTop = textureCoords.top();
      textureBottom = textureCoords.bottom();
      textureLeft = textureCoords.left();
      textureRight = textureCoords.right();

      // Enable the texture
      texture.enable();
      // gl.glEnable(GL_TEXTURE_2D);

      // we want back facing polygons to be filled completely and that we want front
      // facing polygons to be outlined only.
      gl.glPolygonMode(GL_BACK, GL_FILL); // Back Face Is Filled In
      gl.glPolygonMode(GL_FRONT, GL_LINE); // Front Face Is Drawn With Lines

      for (int x = 0; x < numPoints; x++) { // Loop Through The Y Plane
         for (int y = 0; y < numPoints; y++) {
            // Apply The Wave To Our Mesh
            // xmax is 45. Get 9 after dividing by 5. Subtract 4.5 to centralize.
            points[x][y][0] = (float)x / 5.0f - 4.5f;
            points[x][y][1] = (float)y / 5.0f - 4.5f;
            // Sine wave pattern
            points[x][y][2] = (float)(Math.sin(Math.toRadians(x / 5.0f * 40.0f)));
         }
      }
   }

   /**
    * Called by the drawable to initiate OpenGL rendering.
    */
   @Override
   public void display(GLAutoDrawable drawable) {
      // Get the OpenGL graphics context
      GL gl = drawable.getGL();
      // Clear the screen and the depth buffer
      gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      // Reset the view (x, y, z axes back to normal)
      gl.glLoadIdentity();

      gl.glTranslatef(0.0f, 0.0f, -12.0f);
      gl.glRotatef(rotateAnleX, 1.0f, 0.0f, 0.0f);
      gl.glRotatef(rotateAnleY, 0.0f, 1.0f, 0.0f);
      gl.glRotatef(rotateAngleZ, 0.0f, 0.0f, 1.0f);

      // need to flip the image
      float textureHeight = textureTop - textureBottom;

      float x1, y1, x2, y2; // used to break the flag into tiny quads

      gl.glBegin(GL_QUADS);
      for (int x = 0; x < numPoints - 1; x++) {
         for (int y = 0; y < numPoints - 1; y++) {
            x1 = (float)x / 44.0f;
            y1 = (float)y / 44.0f;
            x2 = (float)(x + 1) / 44.0f;
            y2 = (float)(y + 1) / 44.0f;

            // Texture need to flip vertically
            gl.glTexCoord2f(x1, y1 * textureHeight + textureBottom);
            gl.glVertex3f(points[x][y][0], points[x][y][1], points[x][y][2]);

            gl.glTexCoord2f(x1, y2 * textureHeight + textureBottom);
            gl.glVertex3f(points[x][y + 1][0], points[x][y + 1][1],
                  points[x][y + 1][2]);

            gl.glTexCoord2f(x2, y2 * textureHeight + textureBottom);
            gl.glVertex3f(points[x + 1][y + 1][0], points[x + 1][y + 1][1],
                  points[x + 1][y + 1][2]);

            gl.glTexCoord2f(x2, y1 * textureHeight + textureBottom);
            gl.glVertex3f(points[x + 1][y][0], points[x + 1][y][1],
                  points[x + 1][y][2]);
         }
      }
      gl.glEnd();

      if (wiggleCount == 2) { // Used To Slow Down The Wave (Every 2nd Frame Only)
         for (int y = 0; y < 45; y++) {
            float tempHold = points[0][y][2]; // Store current value One Left Side Of
            // Wave
            for (int x = 0; x < 44; x++) {
               // Current Wave Value Equals Value To The Right
               points[x][y][2] = points[x + 1][y][2];
            }
            points[44][y][2] = tempHold; // Last Value Becomes The Far Left Stored
            // Value
         }
         wiggleCount = 0; // Set Counter Back To Zero
      }
      wiggleCount++;

      // update the rotational position after each refresh
      rotateAnleX += roateSpeedX;
      rotateAnleY += rotateSpeedY;
      rotateAngleZ += rotateSpeedZ;
   }

   /**
    * Called by the drawable during the first repaint after the component has been
    * resized. Run at least once when the window is first set to visible.
    */
   @Override
   public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
      // Get the OpenGL graphics context
      GL gl = drawable.getGL();

      if (height == 0) {
         height = 1; // prevent divide by zero
      }
      float aspect = (float)width / height;

      // Set the viewport (display area) to cover the entire window
      gl.glViewport(0, 0, width, height);

      // Enable perspective view (where far objects are smaller)
      gl.glMatrixMode(GL_PROJECTION);
      gl.glLoadIdentity(); // reset
      // View angle of 45 degrees, based on this canvas's aspect ratio. The 0.1f,
      // 100.0f is the starting and ending points for how deep we can draw into
      // the screen.
      glu.gluPerspective(45.0f, aspect, 0.1f, 100.0f); // fovy, aspect, zNear, zFar

      // Enable the model view - any new transformations will affect the model-view
      // matrix
      gl.glMatrixMode(GL_MODELVIEW);
      gl.glLoadIdentity(); // reset
   }

   /**
    * Called when the display mode (eg. resolution) has been changed.
    */
   @Override
   public void displayChanged(GLAutoDrawable drawable, boolean modeChanged,
         boolean deviceChanged) {}
}
